﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using System.IO;
using Microsoft.WindowsAzure;

namespace EncryptDecrypt
{
    public class AzureTableCrypto
    {
        X509Certificate2 theCert = null;
        SymmetricKey symmKeySet = null;
        string KeyTableName =  "AKeyTable";

        private StorageCredentials credentials;
        private string baseAddress;

        EncryptionStrengthEnum EncryptionStrength = EncryptionStrengthEnum.Unknown;

        public AzureTableCrypto(EncryptionStrengthEnum cryptoStrength, string baseAddress, StorageCredentials credentials)
        {
            this.baseAddress = baseAddress;
            this.credentials = credentials;
           this.EncryptionStrength = cryptoStrength;
        }

        X509Certificate2 GetCertificate(string thumbprint)
        {
            StoreName storeName = StoreName.My;
            StoreLocation storeLocation = StoreLocation.LocalMachine;
            X509Store store = new X509Store(storeName, storeLocation);
            X509Certificate2 theCert = null;

            try
            {
                store.Open(OpenFlags.ReadOnly);

                foreach (var cert in store.Certificates)
                {
                    if (cert.HasPrivateKey == false)
                        continue;

                    if (String.Compare(cert.Thumbprint, thumbprint) == 0)
                    {
                        theCert = cert;
                        break;
                    }
                }
            }
            finally
            {
                store.Close();
            }

            // May be null if cert not found.
            return theCert;
        }
        byte[] EncryptStringToBytes_Aes(string plainText, SymmetricKey symKey)
        {
            byte[] Key = symKey.Key;
            byte[] IV = symKey.iv;

            // Check arguments.
            if (plainText == null || plainText.Length <= 0)
                throw new ArgumentNullException("plainText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("Key");
            byte[] encrypted;
            // Create an AesManaged object
            // with the specified key and IV.
            using (AesManaged aesAlg = new AesManaged())
            {
                aesAlg.Key = Key;
                aesAlg.IV = IV;

                // Create a decrytor to perform the stream transform.
                ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

                // Create the streams used for encryption.
                using (MemoryStream msEncrypt = new MemoryStream())
                {
                    using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                    {
                        using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                        {
                            //Write all data to the stream.
                            swEncrypt.Write(plainText);
                        }
                        encrypted = msEncrypt.ToArray();
                    }
                }
            }


            // Return the encrypted bytes from the memory stream.
            return encrypted;

        }
        string DecryptStringFromBytes_Aes(byte[] cipherText, SymmetricKey symKey)
        {
            byte[] Key = symKey.Key;
            byte[] IV = symKey.iv;

            // Check arguments.
            if (cipherText == null || cipherText.Length <= 0)
                throw new ArgumentNullException("cipherText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("Key");

            // Declare the string used to hold
            // the decrypted text.
            string plaintext = null;

            // Create an AesManaged object
            // with the specified key and IV.
            using (AesManaged aesAlg = new AesManaged())
            {
                aesAlg.Key = Key;
                aesAlg.IV = IV;

                // Create a decrytor to perform the stream transform.
                ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

                // Create the streams used for decryption.
                using (MemoryStream msDecrypt = new MemoryStream(cipherText))
                {
                    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                    {
                        using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                        {

                            // Read the decrypted bytes from the decrypting stream
                            // and place them in a string.
                            plaintext = srDecrypt.ReadToEnd();
                        }
                    }
                }

            }
            return plaintext;
        }

        public EncryptedDataResult EncryptString(string dataToEncrypt, int? Version)
        {
            if (symmKeySet == null)
            {
                SymmetricKeyHelper shelp = new SymmetricKeyHelper(baseAddress,credentials, theCert);
                symmKeySet = shelp.LoadSymmetricKey(KeyTableName, Version);

                if (symmKeySet == null)
                    throw new InvalidDataException("The symmetric key with version " + Version + " could not be loaded. Cert:" + theCert.Thumbprint + " table:" + KeyTableName);
            }
           
            // Todo: Determine if key lengths are appropriate for AES.  Using Rijndael values

            // Encrypt the data
            byte[] dataWithEncryptionBytes = EncryptStringToBytes_Aes(dataToEncrypt, symmKeySet);

            EncryptedDataResult ret = new EncryptedDataResult()
            {
                DataByteArray = dataWithEncryptionBytes,
                 KeyVersion = symmKeySet.Version
            };
            return ret;
        }
        /// <summary>
        /// Uses AES to decrypt the data using the session key       
        /// </summary>
        /// <param name="secureData"></param>
        /// <param name="Version">Required parameter to determine which key is needed to decrypt the data.</param>
        /// <returns></returns>
        public string DecryptString(string secureData, int Version)
        {
            var secureDataBytes = Convert.FromBase64String(secureData);
            return DecryptString(secureDataBytes , Version );
        }
        string DecryptString(byte[] secureData, int Version)
        {
            if (symmKeySet == null)
            {
                SymmetricKeyHelper shelp = new SymmetricKeyHelper(baseAddress,credentials , theCert);
                symmKeySet = shelp.LoadSymmetricKey(KeyTableName, Version);

                if (symmKeySet == null)
                    throw new InvalidDataException("The symmetric key with version " + Version + " could not be loaded. Cert:" + theCert.Thumbprint + " table:" + KeyTableName);
            }

            // Decrypt the data using the decrypted Asymmetric key

            string decryptedValue  = DecryptStringFromBytes_Aes(secureData, symmKeySet);

            return decryptedValue;
        }

        public bool LoadCertificateByThumbprint(string p)
        {
            string thumbprint = p.Replace(" ", "").ToUpperInvariant();

            // Get the certificate in the LocalMachine\My store by Thumbprint
            theCert = GetCertificate(thumbprint);

            if (theCert == null)
                throw new InvalidOperationException("The certificate with the thumbprint " + thumbprint + " could not be found.");

            return true;
        }

        internal void TestRSA()
        {
            SymmetricKeyHelper shelp = new SymmetricKeyHelper(baseAddress, credentials, theCert);
            symmKeySet = shelp.TestRSA();
            
        }
    }

}
